Skip to content

feat(traverse): mutation-capable traversal β€” MutablePath with replaceWith/remove/insert/skip/stop - #324

Merged
pyramation merged 1 commit into
mainfrom
feat/traverse-mutation-paths
Jul 30, 2026
Merged

feat(traverse): mutation-capable traversal β€” MutablePath with replaceWith/remove/insert/skip/stop#324
pyramation merged 1 commit into
mainfrom
feat/traverse-mutation-paths

Conversation

@pyramation

Copy link
Copy Markdown
Collaborator

Summary

Stage 2 of the AST convergence plan (constructive-planning #1323): a principled, Babel-inspired mutation API for @pgsql/traverse, added as a new traverse(root, visitor) entry point alongside the untouched read-only walk/visit.

traverse(ast, {
  RangeVar(path) { path.replaceWith({ RangeVar: {...} }); },
  ResTarget: { enter(path) {...}, exit(path) {...} },
  enter(path) { /* catch-all */ }
});

MutablePath extends the existing NodePath shape (tag / node / parent / keyPath) with the container slot (container, containerKey) that physically holds the node, enabling:

  • replaceWith(value) β€” writes container[key] = value; the replacement's children are traversed but the visitor is not re-invoked on the replacement itself (no self-requeue β†’ a visitor replacing a node with the same tag cannot loop)
  • remove() β€” splices from array containers / deletes object fields; children not traversed; sibling array iteration stays aligned (iterators consume the net length delta reported by each visit)
  • insertBefore(...) / insertAfter(...) β€” array containers only; inserted values are not visited
  • skip() / return false β€” skip children (same semantic as walk)
  • stop() β€” halt the whole traversal via a shared TraversalState
  • {enter, exit} handlers per tag plus catch-all enter/exit; exit runs post-order

Traversal is the same runtime-schema-driven walk as walk β€” tagged nodes, tag-synthesized concrete typed fields (e.g. CreatePolicyStmt.table: RangeVar stored bare), untagged ParseResult/ScanResult roots β€” so mutation works uniformly on both storage conventions (replaceWith takes the value exactly as stored: tagged wrapper or bare node data). Mutating the detached root throws.

Deliberately no lexical scope/binding layer β€” SQL object identity lives in the semantic object graph (planning #1322), not the traversal.

14 new tests in mutate.test.ts; 40/40 green in the package.

Link to Devin session: https://app.devin.ai/sessions/025fb88043964fdbb335ac5e39df2478
Requested by: @pyramation

…With/remove/insert/skip/stop and enter/exit visitors
@pyramation pyramation self-assigned this Jul 30, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

πŸ€– Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

βœ… I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

βš™οΈ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit aa8d437 into main Jul 30, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant